﻿using UnityEngine;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System.Linq;

public class MapGenerator
{

    private static MapGrid InstanceGrid(GameObject prefab, int row, int column)
    {
        if (null == prefab)
            return null;

        GameObject go = GameObject.Instantiate(prefab) as GameObject;
        go.transform.position = Map.MapLeftDownPos +
            new Vector3(column * MapGrid.GridWidth, row * MapGrid.GridHeight, 0);
        return go.GetComponent<MapGrid>();
    }

    /// <summary>
    /// 创建一个地图
    /// </summary>
    /// <param name="row"></param>
    /// <param name="column"></param>
    /// <param name="num">尝试次数 尝试次数越多 房间数越多</param>
    /// <param name="roomExtraSize"></param>
    /// <returns></returns>
	public static Map GenerateMap(int row, int column, int num = 20, int roomExtraSize = 0)
    {
        var rooms = new List<Rect>();
        int _currentRegion = -1;
        Map map = new Map(row, column);
        //load grid prefab
        GameObject nonePrefab = Resources.Load("Prefabs/Grids/grid_none") as GameObject;
        GameObject normalPrefab = Resources.Load("Prefabs/Grids/grid_normal") as GameObject;
        GameObject grid_right = Resources.Load("Prefabs/Grids/grid_right") as GameObject;
        GameObject door = Resources.Load("Prefabs/Grids/door") as GameObject;



        #region 生成房间
        {
            for (var i = 0; i < num; i++)
            {
                var size = Random.Range(1, 3 + roomExtraSize) * 2 + 1;
                var rectangularity = Random.Range(0, 1 + size / 2) * 2;
                var width = size;
                var height = size;
                if (Random.Range(0, 1) == 1)
                {
                    width += rectangularity;
                }
                else
                {
                    height += rectangularity;
                }
                var x = Random.Range(0, (column - width) / 2) * 2 + 1;
                var y = Random.Range(0, (row - height) / 2) * 2 + 1;
                var room = new Rect(x, y, width, height);
                var overlaps = false;
                foreach (var other in rooms)
                {
                    if (room.Overlaps(other))
                    {
                        overlaps = true;
                        break;
                    }
                }
                if (overlaps) continue;
                rooms.Add(room);
                _currentRegion++;
            }
            foreach (var one in rooms)
            {
                FillRect(map, grid_right, one);
            }
        }
        #endregion

        #region 生成迷宫
        {
            var unmadeCells = new List<Direction>();
            var cells = new List<IntVector2>();
            for (var y = 1; y < row; y += 2)
            {
                for (var x = 1; x < column; x += 2)
                {
                    if (map.GetMapGrid(y, x) != null)
                    {
                        continue;
                    }
                    _currentRegion++;
                    var pos = new Vector2(x, y);
                    cells.Clear();
                    Direction lastDir = Direction.none;
                    print(map, normalPrefab, new IntVector2(x, y));
                    cells.Add(new IntVector2(pos));
                    while (cells.Count > 0)
                    {
                        for (int i = 0; i < cells.Count; i++)
                        {
                            unmadeCells.Clear();
                            var cell = cells[i];

                            if (_canCarve(map, cell, Direction.top)) { unmadeCells.Add(Direction.top); }
                            if (_canCarve(map, cell, Direction.buttom)) { unmadeCells.Add(Direction.buttom); }
                            if (_canCarve(map, cell, Direction.left)) { unmadeCells.Add(Direction.left); }
                            if (_canCarve(map, cell, Direction.right)) { unmadeCells.Add(Direction.right); }

                            if (unmadeCells.Count > 0)
                            {
                                Direction dir = Direction.none;
                                if (unmadeCells.Contains(lastDir) && Noise1D() > 0.5)
                                {
                                    dir = lastDir;
                                }
                                else
                                {
                                    dir = unmadeCells[Random.Range(0, unmadeCells.Count - 1)];
                                }
                                print(map, normalPrefab, cell + Dir2V2(dir));
                                print(map, normalPrefab, cell + Dir2V2(dir) * 2);
                                cells.Insert(i + 1, cell + Dir2V2(dir) * 2);
                                lastDir = dir;
                            }
                            else
                            {
                                cells.RemoveAt(i);
                                lastDir = Direction.none;
                                break;
                            }
                        }
                    }
                }
            }
        }
        #endregion

        // Find all of the tiles that can connect two (or more) regions.
        var connectorRegions = new Dictionary< IntVector2, HashSet<int>>();
        for (var y = 0; y < row; y++)
        {
            for (var x = 0; x < column; x++)
            {
                var regions = new HashSet<int>();
                if (map.GetMapGrid(y, x) != null) continue;
                var v2 = Dir2V2(Direction.top) * 2 + new IntVector2(y, x);
                if (map.GetMapGrid(v2.x, v2.y) != null && map.GetMapGrid(v2.x, v2.y).Type == MapGrid.GridType.Room) regions.Add(1);
                v2 = Dir2V2(Direction.buttom) * 2 + new IntVector2(y, x);
                if (map.GetMapGrid(v2.x, v2.y) != null && map.GetMapGrid(v2.x, v2.y).Type == MapGrid.GridType.Room) regions.Add(1);
                v2 = Dir2V2(Direction.left) * 2 + new IntVector2(y, x);
                if (map.GetMapGrid(v2.x, v2.y) != null && map.GetMapGrid(v2.x, v2.y).Type == MapGrid.GridType.Room) regions.Add(1);
                v2 = Dir2V2(Direction.right) * 2 + new IntVector2(y, x);
                if (map.GetMapGrid(v2.x, v2.y) != null && map.GetMapGrid(v2.x, v2.y).Type == MapGrid.GridType.Room) regions.Add(1);
                Debug.Log(regions.Count);
                if (regions.Count < 2) continue;
                connectorRegions.Add(new IntVector2(y, x), regions);
            }
        }
        var connectors = connectorRegions.Keys.ToList();

        

        var merged = new List<int>();
        var openRegions = new HashSet<int>();
        for (var i = 0; i <= _currentRegion; i++)
        {
            merged.Add(i);
            openRegions.Add(i);
        }

        // Keep connecting regions until we're down to one.
        /*
        while (openRegions.Count > 1)
        {
            Debug.Log(connectors.Count);
            var connector = connectors[Random.Range(0, connectors.Count - 1)];
            // Carve the connection.
            print(map, door, connector);
            // Merge the connected regions. We'll pick one region (arbitrarily) and
            // map all of the other regions to its index.
            var regions = connectorRegions[connector]
                .Select((region) => merged[region]);
            var dest = regions.First();
            var sources = regions.Skip(1).ToList();
            // Merge all of the affected regions. We have to look at *all* of the
            // regions because other regions may have previously been merged with
            // some of the ones we're merging now.
            for (var i = 0; i <= _currentRegion; i++)
            {
                if (sources.Contains(merged[i]))
                {
                    merged[i] = dest;
                }
            }
            openRegions.RemoveWhere((p) =>
            {
                if (sources.Contains(p))
                {
                    return true;
                }
                return false;
            });
            // Remove any connectors that aren't needed anymore.

            var c = new List<IntVector2>();
            for (int i = 0; i < connectors.Count; i++)
            {
                Vector2 v = new Vector2(connector.x - connectors[i].x, connector.x - connectors[i].y);
                if (v.x < 2 && v.y < 2)
                {
                    regions = connectorRegions[connectors[i]].Select((region) => merged[region]).ToList();
                }

                if (regions.Count() > 1) c.Add(connectors[i]);
                // This connecter isn't needed, but connect it occasionally so that the
                // dungeon isn't singly-connected.
                if (!(Random.Range(0,20) == 0)) print(map, door, connector);
            }
        }*/

        return map;
    }

    private static bool _canCarve(Map map, IntVector2 cell, Direction dir)
    {
        var c = Dir2V2(dir) * 2 + cell;
        
        if (c.x < 0 || c.x >= map.MapColumn || c.y < 0 || c.y >= map.MapRow)
        {
            return false;
        }
        return map.GetMapGrid(c.y, c.x) == null;
    }

    //return [0, 1]
    private static float Noise1D()
    {
        return Random.Range(0, 1.0f);
    }

    private static void print(Map map, GameObject prefab, IntVector2 v2)
    {
        var go = InstanceGrid(prefab, v2.y, v2.x);
        map.SetMapGrid(go, v2.y, v2.x);
    }

    private static void FillRect(Map map, GameObject prefab, Rect r)
    {
        for (int i = 0; i < r.width; i++)
        {
            for (int c = 0; c < r.height; c++)
            {
                int x = (int)(c + r.y);
                int y = (int)(i + r.x);
                var go = InstanceGrid(prefab, x, y);
                map.SetMapGrid(go, x, y);
            }
        }
    }

    private static IntVector2 Dir2V2(Direction dir)
    {
        switch (dir)
        {
            case Direction.top:
                return new IntVector2(0, 1);
            case Direction.buttom:
                return new IntVector2(0, -1);
            case Direction.left:
                return new IntVector2(-1, 0);
            case Direction.right:
                return new IntVector2(1, 0);
            default:
                return new IntVector2(0, 0);
        }
    }

    private enum Direction
    {
        top = 0, buttom = 1, left = 2, right = 3, none = 4
    }
}
